package cn.xinfei.xdecision.data.core.frame.retry;

import cn.xinfei.xdecision.common.model.datax.enums.RetryStrategy;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Slf4j
public class HttpClientManage {

    private static final Logger logger = LoggerFactory.getLogger(HttpClientManage.class);


    private String reqUrl;
    private long connectTimeout;
    private long readTimeOut;
    private long writeTimeOut;
    private int maxRetryNum;
    private long retryTimeout;
    private RetryStrategy retryStrategy;

    private int maxIdle;
    private long keepAliveDuration;

    public static MediaType MEDIATYPE_X_WWW_FROM_URL_ENCORED = MediaType.parse("application/x-www-form-urlencoded");
    public static MediaType MEDIATYPE_JSON = MediaType.parse("application/json; charset=utf-8");
    public static MediaType MEDIATYPE_XML = MediaType.parse("application/xml");
    public static MediaType MEDIATYPE_STREAM = MediaType.parse("application/octet-stream");
    private static Map<String, HttpClientManage> okHttpClientManages = new ConcurrentHashMap<>();
    private OkHttpClient client;

    private HttpClientManage(String reqUrl, long connectTimeout, long readTimeOut, int maxIdle, long keepAliveDuration) {
        super();
        this.reqUrl = reqUrl;
        this.connectTimeout = connectTimeout;
        this.readTimeOut = readTimeOut;
        this.maxIdle = maxIdle;
        this.keepAliveDuration = keepAliveDuration;
        initClient();
    }


    private HttpClientManage(String reqUrl, long connectTimeout,
                             long readTimeOut, long writeTimeOut,
                             RetryStrategy retryStrategy,
                             int maxRetryNum, int maxIdle, long keepAliveDuration) {
        super();
        this.reqUrl = reqUrl;
        this.connectTimeout = connectTimeout;
        this.readTimeOut = readTimeOut;
        this.writeTimeOut = writeTimeOut;
        this.retryStrategy = retryStrategy;
        this.maxRetryNum = maxRetryNum;
        this.maxIdle = maxIdle;
        this.keepAliveDuration = keepAliveDuration;
        initClientWithRetry();
    }


    private HttpClientManage(String reqUrl, long connectTimeout,
                             long readTimeOut, long writeTimeOut,
                             RetryStrategy retryStrategy,
                             int maxRetryNum, long retryTimeout) {
        super();
        this.reqUrl = reqUrl;
        this.connectTimeout = connectTimeout;
        this.readTimeOut = readTimeOut;
        this.writeTimeOut = writeTimeOut;
        this.retryStrategy = retryStrategy;
        this.maxRetryNum = maxRetryNum;
        this.retryTimeout = retryTimeout;
        initClientWithRetry(retryTimeout);
    }

    private HttpClientManage(String reqUrl, long connectTimeout, long readTimeOut, boolean unsafe) {
        super();
        this.reqUrl = reqUrl;
        this.connectTimeout = connectTimeout;
        this.readTimeOut = readTimeOut;
        if (unsafe) {
            initUnsafeClient();
        } else {
            initClient();
        }

    }

    public static HttpClientManage getOkHttpClientManage(String reqUrl) {
        return getOkHttpClientManage(reqUrl, 3000, 5000, 20, 300);
    }

    public static HttpClientManage getOkHttpClientManage(String reqUrl, long connectTimeout, long readTimeOut, int maxIdle, long keepAliveDuration) {
        HttpClientManage okHttpClientManage = okHttpClientManages.get(reqUrl);
        if (okHttpClientManage == null) {
            synchronized (HttpClientManage.class) {
                if (okHttpClientManage == null) {
                    okHttpClientManage = new HttpClientManage(reqUrl, connectTimeout, readTimeOut, maxIdle, keepAliveDuration);
                    okHttpClientManages.put(reqUrl, okHttpClientManage);
                }

            }

        }
        return okHttpClientManage;
    }

    public static HttpClientManage getOkHttpClientManageRetryable(
            String reqUrl,
            long connectTimeout,
            long readTimeOut,
            long writeTimeOut,
            RetryStrategy retryStrategy,
            int maxRetryNum,
            int maxIdle,
            long keepAliveDuration) {
        HttpClientManage okHttpClientManage = okHttpClientManages.get(reqUrl);
        if (null == okHttpClientManage) {
            synchronized (HttpClientManage.class) {
                if (null == okHttpClientManage) {
                    okHttpClientManage = new HttpClientManage(
                            reqUrl, connectTimeout, readTimeOut, writeTimeOut, retryStrategy, maxRetryNum, maxIdle, keepAliveDuration);
                    okHttpClientManages.put(reqUrl, okHttpClientManage);
                }

            }
        }
        return okHttpClientManage;
    }

    @Deprecated
    public static HttpClientManage getOkHttpClientManageUnsafe(String reqUrl, int connectTimeout, int readTimeOut) {
        HttpClientManage okHttpClientManage = okHttpClientManages.get(reqUrl);
        if (okHttpClientManage == null) {
            okHttpClientManage = new HttpClientManage(reqUrl, connectTimeout, readTimeOut, true);
            okHttpClientManages.put(reqUrl, okHttpClientManage);
        }
        return okHttpClientManage;
    }

    @Deprecated
    public static HttpClientManage getOkHttpClientManageUnsafeNew(String reqUrl, int connectTimeout, int readTimeOut) {
        return new HttpClientManage(reqUrl, connectTimeout, readTimeOut, true);
    }

    private void initClient() {
        if (client == null) {
            client = new OkHttpClient().newBuilder()
                    .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                    .readTimeout(readTimeOut, TimeUnit.MILLISECONDS)
                    .retryOnConnectionFailure(true)
                    .connectionPool(new ConnectionPool(maxIdle, keepAliveDuration, TimeUnit.SECONDS))
                    .addInterceptor(new CustomLogInterceptor())
                    .build();
        }
    }

    private void initClientWithRetry() {
        if (client == null) {
            client = new OkHttpClient().newBuilder()
                    .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                    .readTimeout(readTimeOut, TimeUnit.MILLISECONDS)
                    .writeTimeout(writeTimeOut, TimeUnit.MILLISECONDS)
                    .retryOnConnectionFailure(true)
                    .connectionPool(new ConnectionPool(maxIdle, keepAliveDuration, TimeUnit.SECONDS))
                    .addInterceptor(new OkHttpRetryInterceptor(maxRetryNum, retryStrategy, connectTimeout))
                    .addInterceptor(new CustomLogInterceptor())
                    .build();
        }
    }

    private void initClientWithRetry(long retryTimeout) {
        if (client == null) {
            client = new OkHttpClient().newBuilder()
                    .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                    .readTimeout(readTimeOut, TimeUnit.MILLISECONDS)
                    .writeTimeout(writeTimeOut, TimeUnit.MILLISECONDS)
                    .retryOnConnectionFailure(true)
                    .connectionPool(new ConnectionPool(maxIdle, keepAliveDuration, TimeUnit.SECONDS))
                    .addInterceptor(new OkHttpRetryInterceptor(maxRetryNum, retryStrategy, retryTimeout))
                    .addInterceptor(new CustomLogInterceptor())
                    .build();
        }
    }

    private void initUnsafeClient() {
        if (client == null) {
            client = getUnsafeOkHttpClient();
        }
    }

    public String post(String params, MediaType mediaType) throws Exception {
        try {
            log.info("reqUrl=" + reqUrl + " ,requestBody=" + params);
            RequestBody body = RequestBody.create(mediaType, params);
            Request request = new Request.Builder()
                    .url(reqUrl)
                    .post(body)
                    .addHeader("cache-control", "no-cache")
                    .build();
            Response response = client.newCall(request).execute();
            log.info("response=" + response.toString());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            String responseBody = response.body().string();
            log.info("response.body=" + responseBody);
            return responseBody;
        } catch (Exception e) {
            logger.error("", e);
            throw e;
        }
    }

    public String postFrom(Map<String, String> params) throws Exception {
        try {
            log.info("reqUrl=" + reqUrl + " ,requestBody=" + params);
            FormBody.Builder builder = new FormBody.Builder();
            for (Map.Entry<String, String> entry : params.entrySet()) {
                builder.add(entry.getKey(), entry.getValue());
            }
            FormBody formBody = builder.build();
            Request request = new Request.Builder()
                    .url(reqUrl)
                    .post(formBody)
                    .addHeader("cache-control", "no-cache")
                    .build();

            Response response = client.newCall(request).execute();
            log.info("response=" + response.toString());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            return response.body().string();
        } catch (Exception e) {
            logger.error("", e);
            throw e;
        }
    }

    public String put(String params, MediaType mediaType) throws Exception {
        try {
            log.info("reqUrl=" + reqUrl + " ,requestBody=" + params);
            RequestBody body = RequestBody.create(mediaType, params);
            Request.Builder builder = new Request.Builder()
                    .url(reqUrl)
                    .put(body)
                    .addHeader("cache-control", "no-cache");
            Request request = builder.build();
            Response response = client.newCall(request).execute();
            log.info("response=" + response.toString());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            return response.body().string();
        } catch (Exception e) {
            logger.error("", e);
            throw e;
        }
    }

    public String put(String params, MediaType mediaType, Map<String, String> headers) throws Exception {
        try {
            log.info("reqUrl=" + reqUrl + " ,requestBody=" + params);
            RequestBody body = RequestBody.create(mediaType, params);
            Request.Builder builder = new Request.Builder()
                    .url(reqUrl)
                    .put(body)
                    .addHeader("cache-control", "no-cache");
            headers.forEach(builder::addHeader);
            Request request = builder.build();
            Response response = client.newCall(request).execute();
            log.info("response=" + response.toString());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            return response.body().string();
        } catch (Exception e) {
            logger.error("", e);
            throw e;
        }
    }

    public String post(String params, MediaType mediaType, Map<String, String> headers) throws Exception {
        try {
            logger.info("reqUrl=" + reqUrl + " ,requestBody=" + params + " header=" + JSONObject.toJSONString(headers));
            RequestBody body = RequestBody.create(mediaType, params);
            Request.Builder builder = new Request.Builder()
                    .url(reqUrl)
                    .post(body)
                    .addHeader("cache-control", "no-cache");
            if (headers != null && headers.size() > 0) {
                headers.forEach(builder::addHeader);
            }

            Request request = builder.build();
            Response response = client.newCall(request).execute();
            log.info("response=" + response.toString());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            return response.body().string();
        } catch (Exception e) {
            logger.error("", e);
            throw e;
        }
    }

    public String postWithNotVerify(String params, MediaType mediaType, Map<String, String> headers) throws Exception {
        try {
            logger.info("reqUrl=" + reqUrl + " ,requestBody=" + params + " header=" + JSONObject.toJSONString(headers));
            RequestBody body = RequestBody.create(mediaType, params);

            Headers.Builder rpcHeaders = new Headers.Builder();
            if (headers != null && headers.size() > 0) {
                headers.forEach(rpcHeaders::addUnsafeNonAscii);
            }

            Request.Builder builder = new Request.Builder()
                    .url(reqUrl)
                    .post(body)
                    .addHeader("cache-control", "no-cache")
                    .headers(rpcHeaders.build());
            Request request = builder.build();
            Response response = client.newCall(request).execute();
            log.info("response=" + response.toString());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            return response.body().string();
        } catch (Exception e) {
            logger.error("", e);
            throw e;
        }
    }

    public String delete(String params, MediaType mediaType) throws Exception {
        try {
            log.info("reqUrl=" + reqUrl + " ,requestBody=" + params);
            RequestBody body = RequestBody.create(mediaType, params);
            Request request = new Request.Builder()
                    .url(reqUrl)
                    .delete(body)
                    .addHeader("cache-control", "no-cache")
                    .build();
            Response response = client.newCall(request).execute();
            log.info("response=" + response.toString());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            return response.body().string();
        } catch (Exception e) {
            logger.error("", e);
            throw e;
        }
    }

    private HttpUrl getHttpUrl(String url,
                               Map<String, String> params) {
        HttpUrl.Builder newBuilder = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder();

        if (params != null && params.size() > 0) {
            // Set params
            for (Map.Entry<String, String> stringStringEntry : params.entrySet()) {
                newBuilder.addQueryParameter(stringStringEntry.getKey(), stringStringEntry.getValue());
            }
        }
        return newBuilder.build();
    }

    public String get(Map<String, String> headers,
                      Map<String, String> params) throws IOException {
        try {
            HttpUrl requestBody = getHttpUrl(reqUrl, params);
            logger.info("requestBody=" + requestBody);
            Request.Builder builder = new Request.Builder()
                    .url(requestBody)
                    .addHeader("cache-control", "no-cache")
                    .headers(getHeaders(headers))
                    .get();

            Response response = client.newCall(builder.build()).execute();
            logger.info("responseBody=" + response.toString() + ", body=" + response.body());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            return response.body().string();
        } catch (Exception e) {
            logger.error("请求异常！reqUrl=" + reqUrl, e);
            throw e;
        }
    }

    private Headers getHeaders(Map<String, String> headers) {
        if (headers != null && headers.size() > 0) {
            return Headers.of(headers);
        }
        return new Headers.Builder().build();
    }

    public String get(String params) throws Exception {
        try {
            log.info("requestBody=" + params);
            Request request = new Request.Builder()
                    .url(reqUrl)
                    .get()
                    .addHeader("cache-control", "no-cache")
                    .build();
            Response response = client.newCall(request).execute();
            log.info("responseBody=" + response.toString() + ", body=" + response.body());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            return response.body().string();
        } catch (Exception e) {
            logger.error("请求异常！reqUrl=" + reqUrl, e);
            throw e;
        }
    }

    public String get(String params, Map<String, String> headers) throws Exception {
        try {
            log.info("requestBody=" + params);
            Request.Builder builder = new Request.Builder()
                    .url(reqUrl)
                    .get()
                    .addHeader("cache-control", "no-cache");
            if (headers != null && headers.size() > 0) {
                headers.forEach(builder::addHeader);
            }
            Request request = builder.build();
            Response response = client.newCall(request).execute();
            log.info("responseBody=" + response.toString() + ", body=" + response.body());
            if (!response.isSuccessful()) {
                String excepMsg = response.code() + ":" + response.message();
                response.body().close();
                throw new RuntimeException(excepMsg);
            }
            return response.body().string();
        } catch (Exception e) {
            logger.error("请求异常！reqUrl=" + reqUrl, e);
            throw e;
        }
    }

    private OkHttpClient getUnsafeOkHttpClient() {
        try {
            // Create a trust manager that does not validate certificate chains
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            // Create an ssl socket factory with our all-trusting manager
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.sslSocketFactory(sslSocketFactory);
            builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            OkHttpClient okHttpClient = builder.build();
            return okHttpClient;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
